Package org.python.pydev.editor.codecompletion.revisited

Source Code of org.python.pydev.editor.codecompletion.revisited.PythonPathHelper

/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on Nov 12, 2004
*
* @author Fabio Zadrozny
*/
package org.python.pydev.editor.codecompletion.revisited;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.ui.ide.IDE;
import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.ModulesKey;
import org.python.pydev.core.ModulesKeyForZip;
import org.python.pydev.core.docutils.StringUtils;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.editor.codecompletion.revisited.ModulesFoundStructure.ZipContents;
import org.python.pydev.plugin.nature.IPythonPathHelper;
import org.python.pydev.ui.filetypes.FileTypesPreferencesPage;
import org.python.pydev.utils.PyFileListing;
import org.python.pydev.utils.PyFileListing.PyFileInfo;

import com.aptana.shared_core.io.FileUtils;
import com.aptana.shared_core.string.FastStringBuffer;

/**
* This is not a singleton because we may have a different pythonpath for each project (even though
* we have a default one as the original pythonpath).
*
* @author Fabio Zadrozny
*/
public final class PythonPathHelper implements IPythonPathHelper {

    /**
     * This is a list of Files containing the pythonpath. It's always an immutable list. The instance must
     * be changed to change the pythonpath.
     */
    private volatile List<String> pythonpath = Collections.unmodifiableList(new ArrayList<String>());

    /**
     * Returns the default path given from the string.
     * @param str
     * @return a trimmed string with all the '\' converted to '/'
     */
    public static String getDefaultPathStr(String str) {
        //this check is no longer done... could result in other problems
        // if(acceptPoint == false && str.indexOf(".") == 0){ //cannot start with a dot
        //         throw new RuntimeException("The pythonpath can only have absolute paths (cannot start with '.', therefore, the path: '"+str+"' is not valid.");
        // }
        return StringUtils.replaceAllSlashes(str.trim());
    }

    public PythonPathHelper() {
    }

    /**
     * This method returns all modules that can be obtained from a root File.
     * @param monitor keep track of progress (and cancel)
     * @return the listing with valid module files considering that root is a root path in the pythonpath.
     * May return null if the passed file does not exist or is not a directory (e.g.: zip file)
     */
    public static PyFileListing getModulesBelow(File root, IProgressMonitor monitor) {
        if (!root.exists()) {
            return null;
        }

        if (root.isDirectory()) {
            FileFilter filter = new FileFilter() {

                public boolean accept(File pathname) {
                    if (pathname.isFile()) {
                        return isValidFileMod(FileUtils.getFileAbsolutePath(pathname));
                    } else if (pathname.isDirectory()) {
                        return isFileOrFolderWithInit(pathname);
                    } else {
                        return false;
                    }
                }

            };
            return PyFileListing.getPyFilesBelow(root, filter, monitor, true);

        }
        return null;
    }

    /**
     * @param root the zip file to analyze
     * @param monitor the monitor, to keep track of what is happening
     * @return a list with the name of the found modules in the jar
     */
    protected static ModulesFoundStructure.ZipContents getFromZip(File root, IProgressMonitor monitor) {

        String fileName = root.getName();
        if (root.isFile() && FileTypesPreferencesPage.isValidZipFile(fileName)) { //ok, it may be a jar file, so let's get its contents and get the available modules

            //the major difference from handling jars from regular python files is that we don't have to check for __init__.py files
            ModulesFoundStructure.ZipContents zipContents = new ModulesFoundStructure.ZipContents(root);

            //by default it's a zip (for python) -- may change if a .class is found.
            zipContents.zipContentsType = ZipContents.ZIP_CONTENTS_TYPE_PY_ZIP;

            try {
                String zipFileName = root.getName();

                ZipFile zipFile = new ZipFile(root);
                try {
                    Enumeration<? extends ZipEntry> entries = zipFile.entries();

                    int i = 0;
                    FastStringBuffer buffer = new FastStringBuffer();
                    //ok, now that we have the zip entries, let's map them to modules
                    while (entries.hasMoreElements()) {
                        ZipEntry entry = entries.nextElement();
                        String name = entry.getName();
                        if (!entry.isDirectory()) {
                            if (isValidFileMod(name) || name.endsWith(".class")) {

                                if (name.endsWith(".class")) {
                                    zipContents.zipContentsType = ZipContents.ZIP_CONTENTS_TYPE_JAR;
                                }

                                //it is a valid python file
                                if (i % 15 == 0) {
                                    if (monitor.isCanceled()) {
                                        return null;
                                    }
                                    buffer.clear();
                                    monitor.setTaskName(buffer.append("Found in ").append(zipFileName)
                                            .append(" module ").append(name).toString());
                                    monitor.worked(1);
                                }

                                if (isValidInitFile(name)) {
                                    zipContents.pyInitFilesLowerWithoutExtension.add(StringUtils.stripExtension(name)
                                            .toLowerCase());
                                }
                                zipContents.pyFilesLowerToRegular.put(name.toLowerCase(), name);
                            }

                        } else { //!isDirectory
                            zipContents.pyfoldersLower.add(name.toLowerCase());
                        }
                        i++;
                    }
                } finally {
                    zipFile.close();
                }

                //now, on to actually filling the structure if we have a zip file (just add the ones that are actually under
                //the pythonpath)
                zipContents.consolidatePythonpathInfo(monitor);

                return zipContents;

            } catch (Exception e) {
                //that's ok, it is probably not a zip file after all...
                Log.log(e);
            }
        }
        return null;
    }

    /**
     * @return if the path passed belongs to a valid python source file (checks for the extension)
     */
    public static boolean isValidSourceFile(String path) {
        path = path.toLowerCase();
        for (String end : FileTypesPreferencesPage.getDottedValidSourceFiles()) {
            if (path.endsWith(end)) {
                return true;
            }
        }
        if (path.endsWith(".pypredef")) {
            return true;
        }
        return false;
    }

    /**
     * @return whether an IFile is a valid source file given its extension
     */
    public static boolean isValidSourceFile(IFile file) {
        String ext = file.getFileExtension();
        if (ext == null) { // no extension
            return false;
        }
        ext = ext.toLowerCase();
        for (String end : FileTypesPreferencesPage.getValidSourceFiles()) {
            if (ext.equals(end)) {
                return true;
            }
        }
        if (ext.equals(".pypredef")) {
            return true;
        }
        return false;
    }

    /**
     * @return if the paths maps to a valid python module (depending on its extension).
     */
    public static boolean isValidFileMod(String path) {

        boolean ret = false;
        if (isValidSourceFile(path)) {
            ret = true;

        } else if (FileTypesPreferencesPage.isValidDll(path)) {
            ret = true;
        }

        return ret;
    }

    public String resolveModule(String fullPath) {
        return resolveModule(fullPath, false);
    }

    public String resolveModule(String fullPath, ArrayList<String> pythonPathToUse) {
        return resolveModule(fullPath, false, pythonPathToUse);
    }

    public String resolveModule(String fullPath, final boolean requireFileToExist) {
        return resolveModule(fullPath, requireFileToExist, getPythonpath());
    }

    /**
     * DAMN... when I started thinking this up, it seemed much better... (and easier)
     *
     * @param module - this is the full path of the module. Only for directories or py,pyd,dll,pyo files.
     * @return a String with the module that the file or folder should represent. E.g.: compiler.ast
     */
    public String resolveModule(String fullPath, final boolean requireFileToExist, List<String> pythonPathCopy) {
        fullPath = FileUtils.getFileAbsolutePath(fullPath);
        fullPath = getDefaultPathStr(fullPath);
        String fullPathWithoutExtension;

        if (isValidSourceFile(fullPath) || FileTypesPreferencesPage.isValidDll(fullPath)) {
            fullPathWithoutExtension = FullRepIterable.headAndTail(fullPath)[0];
        } else {
            fullPathWithoutExtension = fullPath;
        }

        final File moduleFile = new File(fullPath);

        if (requireFileToExist && !moduleFile.exists()) {
            return null;
        }

        boolean isFile = moduleFile.isFile();

        //go through our pythonpath and check the beginning
        for (String pathEntry : pythonPathCopy) {

            String element = getDefaultPathStr(pathEntry);
            if (fullPath.startsWith(element)) {
                int len = element.length();
                String s = fullPath.substring(len);
                String sWithoutExtension = fullPathWithoutExtension.substring(len);

                if (s.startsWith("/")) {
                    s = s.substring(1);
                }
                if (sWithoutExtension.startsWith("/")) {
                    sWithoutExtension = sWithoutExtension.substring(1);
                }

                if (!isValidModuleLastPart(sWithoutExtension)) {
                    continue;
                }

                s = s.replaceAll("/", ".");
                if (s.indexOf(".") != -1) {
                    File root = new File(element);
                    if (root.exists() == false) {
                        continue;
                    }

                    final List<String> temp = StringUtils.dotSplit(s);
                    String[] modulesParts = temp.toArray(new String[temp.size()]);

                    //this means that more than 1 module is specified, so, in order to get it,
                    //we have to go and see if all the folders to that module have __init__.py in it...
                    if (modulesParts.length > 1 && isFile) {
                        String[] t = new String[modulesParts.length - 1];

                        for (int i = 0; i < modulesParts.length - 1; i++) {
                            t[i] = modulesParts[i];
                        }
                        t[t.length - 1] = t[t.length - 1] + "." + modulesParts[modulesParts.length - 1];
                        modulesParts = t;
                    }

                    //here, in modulesParts, we have something like
                    //["compiler", "ast.py"] - if file
                    //["pywin","debugger"] - if folder
                    //
                    //root starts with the pythonpath folder that starts with the same
                    //chars as the full path passed in.
                    boolean isValid = true;
                    for (int i = 0; i < modulesParts.length && root != null; i++) {
                        root = new File(FileUtils.getFileAbsolutePath(root) + "/" + modulesParts[i]);

                        //check if file is in root...
                        if (isValidFileMod(modulesParts[i])) {
                            if (root.exists() && root.isFile()) {
                                break;
                            }

                        } else {
                            //this part is a folder part... check if it is a valid module (has init).
                            if (isFileOrFolderWithInit(root) == false) {
                                isValid = false;
                                break;
                            }
                            //go on and check the next part.
                        }
                    }
                    if (isValid) {
                        if (isFile) {
                            s = stripExtension(s);
                        } else if (moduleFile.exists() == false) {
                            //ok, it does not exist, so isFile will not work, let's just check if it is
                            //a valid module (ends with .py or .pyw) and if it is, strip the extension
                            if (isValidFileMod(s)) {
                                s = stripExtension(s);
                            }
                        }
                        return s;
                    }
                } else {
                    //simple part, we don't have to go into subfolders to check validity...
                    if (!isFile && moduleFile.isDirectory() && isFileOrFolderWithInit(moduleFile) == false) {
                        return null;
                    }
                    return s;
                }
            }

        }
        //ok, it was not found in any existing way, so, if we don't require the file to exist, let's just do some simpler search and get the
        //first match (if any)... this is useful if the file we are looking for has just been deleted
        if (!requireFileToExist) {
            //we have to remove the last part (.py, .pyc, .pyw)
            for (String element : pythonPathCopy) {
                element = getDefaultPathStr(element);
                if (fullPathWithoutExtension.startsWith(element)) {
                    String s = fullPathWithoutExtension.substring(element.length());
                    if (s.startsWith("/")) {
                        s = s.substring(1);
                    }
                    if (!isValidModuleLastPart(s)) {
                        continue;
                    }
                    s = s.replaceAll("/", ".");
                    return s;
                }
            }
        }
        return null;
    }

    /**
     * Note that this function is not completely safe...beware when using it.
     * @param s
     * @return
     */
    public static String stripExtension(String s) {
        if (s != null) {
            return StringUtils.stripExtension(s);
        }
        return null;
    }

    /**
     * @param root this is the folder we're checking
     * @return true if it is a folder with an __init__ python file
     */
    protected static boolean isFileOrFolderWithInit(File root) {
        //check for an __init__ in a dir (we do not check if it is a file, becase if it is, it should return null)
        String[] items = root.list(new FilenameFilter() {

            public boolean accept(File dir, String name) {
                if (isValidInitFile(name)) {
                    return true;
                }
                return false;
            }

        });
        if (items == null || items.length < 1) {
            return false;
        }

        return true;
    }

    /**
     * @param item the file we want to check
     * @return true if the file is a valid __init__ file
     */
    public static boolean isValidInitFile(String item) {
        return item.toLowerCase().indexOf("__init__.") != -1 && isValidSourceFile(item);
    }

    /**
     * @param s
     * @return
     */
    public static boolean isValidModuleLastPart(String s) {
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '-' || c == ' ' || c == '.' || c == '+') {
                return false;
            }
        }
        return true;
    }

    public void setPythonPath(List<String> newPythonpath) {
        this.pythonpath = Collections.unmodifiableList(new ArrayList<String>(newPythonpath));
    }

    /**
     * @param string with paths separated by |
     * @return
     */
    public void setPythonPath(String string) {
        this.pythonpath = Collections.unmodifiableList(parsePythonPathFromStr(string, new ArrayList<String>()));
    }

    /**
     * @param string this is the string that has the pythonpath (separated by |)
     * @param lPath OUT: this list is filled with the pythonpath (if null an ArrayList is created to fill the pythonpath).
     * @return
     */
    public static List<String> parsePythonPathFromStr(String string, List<String> lPath) {
        if (lPath == null) {
            lPath = new ArrayList<String>();
        }
        String[] strings = string.split("\\|");
        for (int i = 0; i < strings.length; i++) {
            String defaultPathStr = getDefaultPathStr(strings[i]);
            if (defaultPathStr != null && defaultPathStr.trim().length() > 0) {
                File file = new File(defaultPathStr);
                if (file.exists()) {
                    //we have to get it with the appropriate cases and in a canonical form
                    String path = FileUtils.getFileAbsolutePath(file);
                    lPath.add(path);
                } else {
                    lPath.add(defaultPathStr);
                }
            }
        }
        return lPath;
    }

    /**
     * @return a list with the pythonpath, such that each element of the list is a part of
     * the pythonpath
     * @note returns a list that's not modifiable!
     */
    public List<String> getPythonpath() {
        return pythonpath;
    }

    /**
     * This method should traverse the pythonpath passed and return a structure
     * with the info that could be collected about the files that are related to
     * python modules.
     */
    public ModulesFoundStructure getModulesFoundStructure(IProgressMonitor monitor) {
        if (monitor == null) {
            monitor = new NullProgressMonitor();
        }
        List<String> pythonpathList = getPythonpath();

        ModulesFoundStructure ret = new ModulesFoundStructure();

        FastStringBuffer tempBuf = new FastStringBuffer();
        for (Iterator<String> iter = pythonpathList.iterator(); iter.hasNext();) {
            String element = iter.next();

            if (monitor.isCanceled()) {
                break;
            }

            //the slow part is getting the files... not much we can do (I think).
            File root = new File(element);
            PyFileListing below = getModulesBelow(root, monitor);
            if (below != null) {

                Iterator<PyFileInfo> e1 = below.getFoundPyFileInfos().iterator();
                while (e1.hasNext()) {
                    PyFileInfo pyFileInfo = e1.next();
                    File file = pyFileInfo.getFile();
                    String modName = pyFileInfo.getModuleName(tempBuf);
                    if (isValidModuleLastPart(FullRepIterable.getLastPart(modName))) {
                        ret.regularModules.put(file, modName);
                    }
                }

            } else { //ok, it was null, so, maybe this is not a folder, but zip file with java classes...
                ModulesFoundStructure.ZipContents zipContents = getFromZip(root, monitor);
                if (zipContents != null) {
                    ret.zipContents.add(zipContents);
                }
            }
        }
        return ret;
    }

    /**
     * @param workspaceMetadataFile
     * @throws IOException
     */
    public void loadFromFile(File pythonpatHelperFile) throws IOException {
        String fileContents = FileUtils.getFileContents(pythonpatHelperFile);
        if (fileContents == null || fileContents.trim().length() == 0) {
            throw new IOException("No loaded contents from: " + pythonpatHelperFile);
        }
        this.pythonpath = Collections.unmodifiableList(StringUtils.split(fileContents, '\n'));
    }

    /**
     * @param pythonpatHelperFile
     */
    public void saveToFile(File pythonpatHelperFile) {
        FileUtils.writeStrToFile(com.aptana.shared_core.string.StringUtils.join("\n", this.pythonpath), pythonpatHelperFile);
    }

    public static boolean canAddAstInfoFor(ModulesKey key) {
        if (key.file != null && key.file.exists()) {

            if (PythonPathHelper.isValidSourceFile(key.file.getName())) {
                return true;
            }

            boolean isZipModule = key instanceof ModulesKeyForZip;
            if (isZipModule) {
                ModulesKeyForZip modulesKeyForZip = (ModulesKeyForZip) key;
                if (PythonPathHelper.isValidSourceFile(modulesKeyForZip.zipModulePath)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @return true if PyEdit.EDITOR_ID is set as the persistent property (only if the file does not have an extension).
     */
    public static boolean markAsPyDevFileIfDetected(IFile file) {
        String name = file.getName();
        if (name == null || name.indexOf('.') != -1) {
            return false;
        }

        String editorID;
        try {
            editorID = file.getPersistentProperty(IDE.EDITOR_KEY);
            if (editorID == null) {
                InputStream contents = file.getContents(true);
                Reader inputStreamReader = new InputStreamReader(new BufferedInputStream(contents));
                if (FileUtils.hasPythonShebang(inputStreamReader)) {
                    IDE.setDefaultEditor(file, PyEdit.EDITOR_ID);
                    return true;
                }
            } else {
                return PyEdit.EDITOR_ID.equals(editorID);
            }

        } catch (Exception e) {
            if (file.exists()) {
                Log.log(e);
            }
        }
        return false;
    }

}
TOP

Related Classes of org.python.pydev.editor.codecompletion.revisited.PythonPathHelper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.